What we have here is a typical service contract that supports callbacks.
A few key items of note: first, the namespace applied to the service
contract is the value used for the XML payload. Thus, I used the
namespace value of the inbound schema. Secondly, the name of the
parameter in the PublishAdverseEvent
operation signature will be the name of the root node in the message.
Hence, I chose to name the parameter after the data type to ensure a
properly built schema. Thirdly, the callback operation requires the XmlSerializerFormat directive for its response from BizTalk. Without explicitly switching from the default DataContractSerializer,
the callback parameter will not be properly interpreted. Finally, the
SOAP action of the callback operation must be equal to the name of the
initial operation with a Response suffix. So which values in this contract do not
matter to BizTalk? The interface names and operation names are
completely irrelevant. Feel free to use values that best describe the
interaction taking place.
Before we use this contract
in our client code, we should set up the appropriate endpoint in the
client application's configuration. In this case, our endpoint contract
is the one constructed above, the binding is the wsDualHttpBinding, and the address should match the value specified by our in-process receive location.
Now we can dive into our
client code. Because we don't have a proxy class available, we'll need
to dive into the lower-levels of WCF and interact directly with our
channel. In this case, we capitalize on the DuplexChannelFactory object which enables us to assign the instance context containing the callback object.
private static void BizTalkAEDuplexEndpoint()
{
Console.WriteLine("Calling BizTalk duplex service ...");
//create instance context
InstanceContext context = new InstanceContext (new BizTalkAECallbackHandler());
//need to use factory since don’t have proxy available
DuplexChannelFactory<IBizTalkAdverseEventDuplex> factory = new DuplexChannelFactory<IBizTalkAdverseEventDuplex> (context, "BizTalkAEDuplexEndpoint");
IBizTalkAdverseEventDuplex channel = factory.CreateChannel();
try
{
BizTalkAdverseEvent newAE =
new BizTalkAdverseEvent();
newAE.PatientID = "100912";
newAE.PhysicianID = "7543";
newAE.Product = "Cerinob";
newAE.ReportedBy = "Patient";
newAE.Category = "InjectionSoreness";
newAE.DateStarted = new DateTime(2008, 10, 29).ToShortDateString();
newAE.Description = "none";
channel.PublishAdverseEvent(newAE);
//Reader TODO; pick where to close this proxy AFTER callback is received
//((IClientChannel)channel).Close();
//factory.Close();
Console.WriteLine("Doing other things ...");
Console.ReadLine();
}
catch (System.ServiceModel.CommunicationException) { ((IClientChannel)channel).Abort(); }
catch (System.TimeoutException) { ((IClientChannel)channel).Abort(); }
catch (System.Exception) { ((IClientChannel)channel).Abort(); throw; }
}
Much like our earlier duplex example, we have a class (BizTalkAECallbackHandler), which implements our callback contract and deals with the operation invocation in the proper manner.
So is this it? Not quite. When we
transmit a message from the client, our BizTalk bus throws an
exception. Because we are not using WCF message contracts (which are
typically created by service references) but rather data contracts to
make our request, the payload is wrapped with the operation name (
PublishAdverseEvent). The problem is, BizTalk Server doesn't have any message type or subscriptions that match that root value.
This is where a very handy WCF adapter capability comes to the rescue. If you can recall, the Messages
tab of the adapter configuration enables us to specify where to find
the body of the inbound message. Typically we keep the default value of Body,
which simply yanks out the structure contained in the SOAP message body
element. For this situation, we need to dig a bit deeper and pull out
the node beneath the SOAP body's root element. This is accomplished by
setting the Path value, which in our case looks like this:
/*[local-name()='PublishAdverseEvent']/*[local-name()='BizTalkAdverseEvent']
Notice that the parameter in this adapter is called Path
and not "XPath". This is on purpose. This XML search string only works
in a forward fashion so the complete XPath universe is not available.
That is, you cannot execute XPath commands that take a gander up and
down the XML node tree.
With this change in place,
we can now publish messages to BizTalk, and rely on the receive location
to maintain the necessary duplex session while waiting for the
orchestration to respond. While fairly hidden from public view, BizTalk
does indeed support this interesting WCF binding and makes it possible
to design rich callback scenarios between WCF service clients and
BizTalk Server.